/*
 * OUTTER RADIUS(PLANNING/REGROUP - 490
 * INNER RADIUS (SHO RANGE) - 346
 * 
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ai;

import comm.Messenger;
import control.Angvel;
import control.PDController;
import control.Shoot;
import control.Speed;
import control.TankAction;
import control.Utility;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.List;
import java.util.Stack;
import plot.GNUPlot;
import search.AStarSearch;
import search.Searcher;
import world.Base;
import world.Enemy;
import world.Flag;
import world.Friend;
import world.Point;
import world.World;

/**
 *
 * @author dheath
 */
//MULTI AGENT!
public class MultiAgent implements Intelligence
{

    private int botId;
    private World world;
    private Messenger msgr;
    private MAState state;
    private Searcher searcher;
    private boolean sniper;
    private Date lastTime;

    public MultiAgent(int botId)
    {
        this.botId = botId;
        searcher = new Searcher(new AStarSearch());

        setState(new NoFlagOutsideRange());

        lastTime = new Date();
        try
        {
            InputStreamReader isr = new InputStreamReader(System.in);
            BufferedReader stdin = new BufferedReader(isr);

            System.out.println("Will this tank Snipe?");
            String ans = stdin.readLine();
            if (ans.equalsIgnoreCase("y"))
            {
                this.sniper = true;
            } else
            {
                this.sniper = false;
            }
        //stdin.close();
        } catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    public void setWorld(World world)
    {
        this.world = world;
    }

    public void setMessenger(Messenger msgr)
    {
        this.msgr = msgr;
    //searcher.buildSearchGraph(msgr.buildWorld());
    }

    public void setState(MAState state)
    {
        this.state = state;
    }

    public void NextMove()
    {
        state.nextMove(this);
    }

    public World getWorld()
    {
        return this.world;
    }

    public Messenger getMessenger()
    {
        return this.msgr;
    }

    public Searcher getSearcher()
    {
        return this.searcher;
    }

    public int getBotId()
    {
        return this.botId;
    }

    public boolean isSniper()
    {
        return sniper;
    }

    public void setSniper(boolean sniper)
    {
        this.sniper = sniper;
    }

    public Date getLastTime()
    {
        return lastTime;
    }

    public void setLastTime(Date lastTime)
    {
        this.lastTime = lastTime;
    }
}

class MAState
{

    protected final double outerDist = -13.0;
    protected final double innerDist = 346.0;

    public void nextMove(MultiAgent ma)
    {
    }
}

class NoFlagOutsideRange extends MAState
{

    private boolean havePath;
    private PDController controller;
    private Stack<Point> path;
    private Point currentPoint;

    public NoFlagOutsideRange()
    {
        havePath = false;
        controller = new PDController();
    }

    public void nextMove(MultiAgent ma)
    {
        Friend me = ma.getWorld().getFriends().get(ma.getBotId());

        if (me.getLocation().getX() > outerDist)
        {
            (new Speed(0.0)).performAction(ma.getMessenger(), ma.getBotId());
            (new Angvel(0.0)).performAction(ma.getMessenger(), ma.getBotId());
            if (ma.isSniper())
            {
                ma.setState(new NoFlagInRangeSnipe());
            } else
            {
                ma.setState(new NoFlagInRangeDecoy(ma));
            }
        }


        if (!havePath)
        {
            Flag end = null;
            for (Flag flag : ma.getWorld().getFlags())
            {
                if (!flag.getOwnerColor().equalsIgnoreCase(me.getColor()))
                {
                    end = flag;
                }
            }
            GNUPlot plot = new GNUPlot(ma.getWorld(), false, true, "multiagent.plt", .00000000001);

            path = ma.getSearcher().search(me.getLocation(), end.getLocation(), ma.getWorld(), plot);//PDController.setPath()
            currentPoint = path.pop();
            // System.out.println("new point: " + currentPoint);


            //Point p = path.pop();
            /*while(!path.empty())
            {
            Point p = path.pop();
            plot.printX(p, 2);
            }*/

            plot.closeConnection();
            havePath = true;
        } else
        {

            if (!path.empty())
            {
                if (Utility.distance(me.getLocation(), currentPoint) < 50)
                {
                    currentPoint = path.pop();

                }
            }

            Date now = new Date();
            long dT = now.getTime() - ma.getLastTime().getTime();
            ma.setLastTime(now);

            System.out.println("change in Time: " + dT);

            List<TankAction> actions = controller.move(me.getAngle(), me.getLocation(), currentPoint, dT);

            for (TankAction action : actions)
            {
                if(!(action instanceof Shoot))
                {
                    action.performAction(ma.getMessenger(), me.getBotId());
                }
            }
        }

    }
}

class NoFlagNoEnemy extends MAState
{

    private PDController controller;

    public NoFlagNoEnemy()
    {
        System.out.println("GO FOR FLAG!");
        this.controller = new PDController();
    }

    public void nextMove(MultiAgent ma)
    {
        Friend me = ma.getWorld().getFriends().get(ma.getBotId());

        boolean hasFlag = false;
        for(Friend us : ma.getWorld().getFriends())
        {
            if (!us.getFlag().equalsIgnoreCase("none"))
            {                
                hasFlag = true;
            }
        }
        if(hasFlag)
        {
            ma.setState(new HasFlag());
            return;
        }
        ////////////////////////////////////
        // get attractive fields
        /////////////////////////////////////
        Point deltaXY = new Point(0, 0);
        int count = 0;
        for (Flag flag : ma.getWorld().getFlags())
        {
            if (!flag.getOwnerColor().equalsIgnoreCase(me.getColor()))
            {
                double radius = 0;
                Point p = Reactive.calulateAttractiveField(me.getLocation(), flag.getLocation(), radius, Double.parseDouble(ma.getWorld().getConstants().get("worldsize")) * 1.5);
                deltaXY.setX(p.getX() + deltaXY.getX());
                deltaXY.setY(p.getY() + deltaXY.getY());
                count++;
                break;
            }
        }

        deltaXY.setX(deltaXY.getX() / ((double) count));
        deltaXY.setY(deltaXY.getY() / ((double) count));

        deltaXY.setX(me.getLocation().getX() + deltaXY.getX());
        deltaXY.setY(me.getLocation().getY() + deltaXY.getY());

        Date now = new Date();
        long dT = now.getTime() - ma.getLastTime().getTime();
        ma.setLastTime(now);

        System.out.println("change in Time: " + dT);

        List<TankAction> actions = controller.move(me.getAngle(), me.getLocation(), deltaXY, dT);

        for (TankAction action : actions)
        {
            action.performAction(ma.getMessenger(), me.getBotId());
        }
    }
}

class HasFlag extends MAState
{

    private boolean havePath;
    private PDController controller;
    private Stack<Point> path;
    private Point currentPoint;

    public HasFlag()
    {
        System.out.println("GO HOME NOW!");
        havePath = false;
        controller = new PDController();
    }

    public void nextMove(MultiAgent ma)
    {
        Friend me = ma.getWorld().getFriends().get(ma.getBotId());

        if (!havePath)
        {
            Base end = null;
            for (Base base : ma.getWorld().getBases())
            {
                if (base.getTeamColor().equalsIgnoreCase(me.getColor()))
                {
                    end = base;
                    break;
                }
            }
            GNUPlot plot = new GNUPlot(ma.getWorld(), false, true, "multiagent.plt", .00000000001);

            path = ma.getSearcher().search(me.getLocation(), end.getLocation(), ma.getWorld(), plot);//PDController.setPath()
            currentPoint = path.pop();
            // System.out.println("new point: " + currentPoint);


            //Point p = path.pop();
            /*while(!path.empty())
            {
            System.out.println("plotting point");
            Point p = path.pop();
            plot.printX(p, 2);
            }
             */
            plot.closeConnection();
            havePath = true;
        } else
        {

            if (!path.empty())
            {
                if (Utility.distance(me.getLocation(), currentPoint) < 50)
                {
                    currentPoint = path.pop();
                //System.out.println("new point: " + currentPoint);
                }
            }

            Date now = new Date();
            long dT = now.getTime() - ma.getLastTime().getTime();
            ma.setLastTime(now);

            System.out.println("change in Time: " + dT);

            List<TankAction> actions = controller.move(me.getAngle(), me.getLocation(), currentPoint, dT);

            for (TankAction action : actions)
            {
                action.performAction(ma.getMessenger(), me.getBotId());
            }
        }

    }
}

class NoFlagInRangeDecoy extends MAState
{

    private PDController cont;
    private Point target;
    private Point pFlag;
    private boolean linedUp;
    private boolean forward;

    public NoFlagInRangeDecoy(MultiAgent ma)
    {
        linedUp = false;
        forward = true;

        System.out.println("Start decoy!");
        cont = new PDController();

        Friend me = ma.getWorld().getFriends().get(ma.getBotId());
        pFlag = null;
        for (Flag flag : ma.getWorld().getFlags())
        {
            if (!flag.getOwnerColor().equalsIgnoreCase(me.getColor()))
            {
                pFlag = flag.getLocation();
                break;
            }
        }

        double z = Math.sqrt((innerDist * innerDist) / 2);
        target = new Point(pFlag.getX() - z, pFlag.getY() + z);

    }
    // we don't have flag, and in outside range
    public void nextMove(MultiAgent ma)
    {
        Friend me = ma.getWorld().getFriends().get(ma.getBotId());

        // if there are no enemies left
        boolean isEnemy = false;
        for (Enemy e : ma.getWorld().getEnemies())
        {
            if (!e.getStatus().equalsIgnoreCase("dead"))
            {
                isEnemy = true;
                break;
            }
        }
        if (!isEnemy)
        {
            ma.setState(new NoFlagNoEnemy());
            return;
        }

        if (linedUp)
        {
            if (forward)
            {

                if (Utility.distance(me.getLocation(), pFlag) > innerDist && me.getLocation().getY() > 0.0)
                {
                    forward = false;
                    (new Speed(-0.7)).performAction(ma.getMessenger(), ma.getBotId());
                }
                (new Speed(.7)).performAction(ma.getMessenger(), ma.getBotId());
            } else
            {
                if (Utility.distance(me.getLocation(), pFlag) > innerDist && me.getLocation().getY() < 0.0)
                {
                    forward = true;
                    (new Speed(.7)).performAction(ma.getMessenger(), ma.getBotId());
                }
                (new Speed(-0.7)).performAction(ma.getMessenger(), ma.getBotId());
            }
        } else
        {

            Date now = new Date();
            long dT = now.getTime() - ma.getLastTime().getTime();
            ma.setLastTime(now);

            System.out.println("change in Time: " + dT);

            List<TankAction> actions = cont.move(me.getAngle(), me.getLocation(), target, dT);
            for (TankAction a : actions)
            {
                if (a instanceof Angvel)
                {
                    if (a.getError() < .1)// 10 degrees
                    {
                        linedUp = true;
                        (new Angvel(0.0)).performAction(ma.getMessenger(), ma.getBotId());
                        break;
                    }
                    a.performAction(ma.getMessenger(), ma.getBotId());
                }

            }
            (new Speed(0.0)).performAction(ma.getMessenger(), ma.getBotId());
        }

    }
}

class NoFlagInRangeSnipe extends MAState
{

    private boolean startHunt;
    private Date lastTime;
    private PDController controller;
    private long waitTime;

    public NoFlagInRangeSnipe()
    {
        System.out.println("Start sniping!");
        startHunt = false;
        lastTime = new Date();
        controller = new PDController();
        waitTime = 14000;
    }

    public void nextMove(MultiAgent ma)
    {
        boolean allFriends = true;
        Friend teammate = null;
        for (Friend f : ma.getWorld().getFriends())
        {
            if (f.getLocation().getX() <= outerDist)
            {
                allFriends = false;
            }
            teammate = f;
        }

        if (!allFriends)
        {
            (new Speed(0.0)).performAction(ma.getMessenger(), ma.getBotId());
            (new Angvel(0.0)).performAction(ma.getMessenger(), ma.getBotId());
            lastTime = new Date();
            waitTime = 14000;
        } else
        {
            if (!startHunt)
            {
                (new Speed(0.0)).performAction(ma.getMessenger(), ma.getBotId());
                (new Angvel(0.0)).performAction(ma.getMessenger(), ma.getBotId());
                Date thisTime = new Date();
                if (thisTime.getTime() - lastTime.getTime() > waitTime && teammate.getLocation().getX() > outerDist+40 )
                {
                    startHunt = true;
                }
            } else
            {
                Friend me = ma.getWorld().getFriends().get(ma.getBotId());
                Enemy it = null;
                for (Enemy e : ma.getWorld().getEnemies())
                {
                    //System.out.println(e.getStatus()+" - enemy status");
                    if (!e.getStatus().equalsIgnoreCase("dead"))
                    {
                        it = e;
                        break;
                    }
                }
                if (it == null)
                {
                    ma.setState(new NoFlagNoEnemy());
                } else
                {
                    Date now = new Date();
                    long dT = now.getTime() - ma.getLastTime().getTime();
                    ma.setLastTime(now);

                    System.out.println("change in Time: " + dT);

                    List<TankAction> actions = controller.move(me.getAngle(), me.getLocation(), it.getLocation(), dT);

                    for (TankAction a : actions)
                    {
                        if (a instanceof Angvel)
                        {
                            a.performAction(ma.getMessenger(), ma.getBotId());
                        }
                    }
                    if (Utility.distance(me.getLocation(), it.getLocation()) < innerDist)
                    {
                        (new Shoot()).performAction(ma.getMessenger(), ma.getBotId());
                        (new Speed(0.0)).performAction(ma.getMessenger(), ma.getBotId());
                    } else
                    {
                        (new Speed(0.5)).performAction(ma.getMessenger(), ma.getBotId());
                    }
                }
            }
        }
    }
}
